﻿//===============================================================================
// Apolineo Patterns & Practices LeanEngine Framework
//===============================================================================
// Copyright © Apolineo Desenvolvimento de Sistemas Ltda.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using LeanEngine.SalesConcept.Entity;
using LeanEngine.SalesConcept.Repository;

using LeanEngine.Extensions;
using LeanEngine.Collections;
using LeanEngine.Data;
using LeanEngine.Business;
using LeanEngine.Data.Linq;
using LeanEngine.Exception;

namespace LeanEngine.SalesConcept.Business
{
    //Classe de regra de negócio de venda

    //Aqui herdamos da classe BusinessCrud<T> que implementa todo
    //as operações básicas (inserção, atualização, exclusão, leitura)
    internal sealed class BOSale
    {
        #region Singleton Members

        private static BOSale _instance = new BOSale();

        private BOSale() { }

        public static BOSale Instance
        {
            get { return _instance; }
        }

        #endregion

        #region ISale Members

        //Método de inclusão de vendas
        public Sale DoSale(Sale sale)
        {
            //Aqui utilizamos o método de extensão IsValid (Business) para execução
            //todos os atributos de validação da classe Sale 
            if (sale.IsValid() && IsAllDetailListValid(sale.SaleDetailsList))
            {
                var products = VerifyProductStockAvailability(sale);

                //Aqui utilizamos a classe DataTransaction (Data) para dar um contexto
                //transacional a todos os comandos executados após sua criação

                //A DataTransaction é uma transação de banco de dados e o lob se encarrega
                //de passar a transação aos comandos executados, simplificando muito sua
                //utilização e fazendo rolback automaticamente caso a transação não seja
                //completada (através do método Complete)
                using (var trans = new DataTransaction())
                {
                    //Aqui utilizamos o método de extensão do lob para inserir os dados
                    //da classe no banco de dados (Business)
                    sale.Insert();

                    //Aqui utilizamos o método de extensão criado na classe ProductRepository
                    //para dar baixa no estoque dos produtos vendidos
                    products.UpdateStock();

                    for (int i = 0; i < sale.SaleDetailsList.Count; i++)
                    {
                        sale.SaleDetailsList[i].Sale = sale;
                        sale.SaleDetailsList[i].Insert();
                    }

                    //Aqui fazemos o comit da transação
                    trans.Complete();
                }
            }

            return sale;        
        }

        //Método de exclusão de vendas
        public void DeleteSale(long saleId)
        {
            Sale dto = new Sale() { Id = saleId };

            //Aqui utilizamos a classe DataTransaction (Data) para dar um contexto
            //transacional a todos os comandos executados após sua criação

            //A DataTransaction é uma transação de banco de dados e o lob se encarrega
            //de passar a transação aos comandos executados, simplificando muito sua
            //utilização e fazendo rolback automaticamente caso a transação não seja
            //completada (através do método Complete)
            using (var trans = new DataTransaction())
            {
                //Aqui executamos o método de extensão da classe SaleRepository para
                //excluir os itens da venda antes de excluir a venda
                dto.DeleteSaleDetails();

                //Aqui utilizamos o método de extensão do lob para excluir os dados
                //da classe do banco de dados (Business)
                dto.Delete();

                //Aqui fazemos o comit da transação
                trans.Complete();
            }
        }

        public PagedList<Sale> ListSales(SaleSearchCriteria searchCriteria)
        {
            PagedList<Sale> pagedList = new PagedList<Sale>()
            {
                PageIndex = searchCriteria.PageIndex,
                PageSize  = searchCriteria.PageSize,
                TotalItems = SaleRepository.CountSales(searchCriteria)
            };

            if (pagedList.TotalItems.IsGreaterThan(0))
            {
                pagedList.Items = SaleRepository.ListSales(searchCriteria);
            }

            return pagedList;
        }

        #endregion

        #region Methods

        private static bool IsAllDetailListValid(ChangeTrackerCollection<SaleDetail> saleDetailList)
        {
            var notValids = from SaleDetail s in saleDetailList
                            where !s.IsValid()
                            select s;

            return notValids.Count() == 0;
        }

        private IEnumerable<Product> VerifyProductStockAvailability(Sale sale)
        {
            //Aqui usamos o linq para separar os produtos vendidos, porque, podemos
            //ter o mesmo produto em mais de um item de venda
            var productsSolds = (from x in sale.SaleDetailsList
                                 group x by x.Product into g
                                 select g.Key);

            foreach (var product in productsSolds)
            {
                //Aqui usamos o linq para somar a quantidade necessária de um produto
                //específico para a venda
                var quantityNeeded = (from x in sale.SaleDetailsList
                                      where x.Product.Id == product.Id
                                      select x.Quantity).Sum();

                //Aqui executamos o método de extensão criado na classe ProductRepository
                //para verificar se há quantidade suficiente de itens em estoque 
                if (product.HasItensInStock(quantityNeeded))
                {
                    product.QuantityInStock -= quantityNeeded;
                }
                else
                {
                    throw new BusinessException("There is no quantity available for product " + product.Name + ".");
                }
            }

            return productsSolds;
        }

        #endregion
    }
}
